A Star Algorithm 总结与实现

A*(A-Star)算法是一种静态路网中求解最短路最有效的方法。公式表示为:f(n)=g(n)+h(n),其中f(n)是节点n从初始点到目标点的估价函数,g(n)是在状态空间中从初始节点到n节点的实际代价,h(n)是从n到目标节点最佳路径的估计代价。

算法比较

Dijkstra算法从物体所在的初始点开始,访问图中的结点。它迭代检查待检查结点集中的结点,并把和该结点最靠近的尚未检查的结点加入待检查结点集。该结点集从初始结点向外扩展,直到到达目标结点。Dijkstra算法保证能找到一条从初始点到目标点的最短路径,只要所有的边都有一个非负的代价值。(我说“最短路径”是因为经常会出现许多差不多短的路径。)在下图中,粉红色的结点是初始结点,蓝色的是目标点,而类菱形的有色区域(注:原文是teal areas)则是Dijkstra算法扫描过的区域。颜色最淡的区域是那些离初始点最远的,因而形成探测过程(exploration)的边境(frontier):

对于有障碍物的情况下,Dijkstra算法运行得较慢,但确实能保证找到一条最短路径:

最佳优先搜索(BFS)算法按照类似的流程运行,不同的是它能够评估(称为启发式的)任意结点到目标点的代价。与选择离初始结点最近的结点不同的是,它选择离目标最近的结点。BFS不能保证找到一条最短路径。然而,它比Dijkstra算法快的多,因为它用了一个启发式函数(heuristic function)快速地导向目标结点。例如,如果目标位于出发点的南方,BFS将趋向于导向南方的路径。在下面的图中,越黄的结点代表越高的启发式值(移动到目标的代价高),而越黑的结点代表越低的启发式值(移动到目标的代价低)。这表明了与Dijkstra 算法相比,BFS运行得更快。

对于有障碍物的情况,BFS运行得较快,但是它找到的路径明显不是一条好的路径:

A * Algorithm

1968年发明的A star算法就是把启发式方法(heuristic approaches)如BFS,和常规方法如Dijsktra算法结合在一起的算法。有点不同的是,类似BFS的启发式方法经常给出一个近似解而不是保证最佳解。然而,尽管A star基于无法保证最佳解的启发式方法,A star却能保证找到一条最短路径。

在简单的情况中,它和BFS一样快,在凹型障碍物的例子中,A*找到一条和Dijkstra算法一样好的路径.

A star把Dijkstra算法(靠近初始点的结点)和BFS算法(靠近目标点的结点)的信息块结合起来。在讨论A star的标准术语中,g(n)表示从初始结点到任意结点n的代价,h(n)表示从结点n到目标点的启发式评估代价(heuristic estimated cost)。在上图中,yellow(h)表示远离目标的结点而teal(g)表示远离初始点的结点。当从初始点向目标点移动时,A star权衡这两者。每次进行主循环时,它检查f(n)最小的结点n,其中f(n) = g(n) + h(n)。

启发函数

启发式函数h(n)告诉A*从任意结点n到目标结点的最小代价评估值。选择一个好的启发式函数是重要的。

  • 一种极端情况,如果h(n)是0,则只有g(n)起作用,此时A演变成Dijkstra算法,这保证能找到最短路径。
  • 如果h(n)经常都比从n移动到目标的实际代价小(或者相等),则A保证能找到一条最短路径。h(n)越小,A扩展的结点越多,运行就得越慢。
  • 如果h(n)精确地等于从n移动到目标的代价,则A star将会仅仅寻找最佳路径而不扩展别的任何结点,这会运行得非常快。尽管这不可能在所有情况下发生,你仍可以在一些特殊情况下让它们精确地相等(译者:指让h(n)精确地等于实际值)。只要提供完美的信息,A star会运行得很完美,认识这一点很好。
  • 如果h(n)有时比从n移动到目标的实际代价高,则A star不能保证找到一条最短路径,但它运行得更快。
  • 另一种极端情况,如果h(n)比g(n)大很多,则只有h(n)起作用,A star演变成BFS算法。

算法流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OPEN = priority queue containing START
CLOSED = empty set
while lowest rank in OPEN is not the GOAL:
current = remove lowest rank item from OPEN
add current to CLOSED
for neighbors of current:
cost = g(current) + movementcost(current, neighbor)
if neighbor in OPEN and cost less than g(neighbor):
remove neighbor from OPEN, because new path is better
if neighbor in CLOSED and cost less than g(neighbor): ⁽²⁾
remove neighbor from CLOSED
if neighbor not in OPEN and neighbor not in CLOSED:
set g(neighbor) to cost
add neighbor to OPEN
set priority queue rank to g(neighbor) + h(neighbor)
set neighbor's parent to current

reconstruct reverse path from goal to start
by following parent pointers

图解算法

下面通过图解的方式解释A star 算法的工作流程。

如图所示,绿色点为start设为A,红色点为goal设为B,蓝色点为不可通过的障碍物,黑色点为自由区域。目标是规划从A到B的路径。

开始

  1. 搜索的从A点开始,首先将A点加入开启列表,此时取开启列表中的最小值,初始阶段开启列表中只有A一个节点,因此将A点从开启列表中取出,将A点加入关闭列表。

  2. 取出A点的相邻点,将相邻点加入开启列表。如图所示,此时A点即为相邻点的父节点。图中箭头指向父节点。将相邻点与A点加入追溯表中。

计算评分

对相邻点,一次计算每一点的g,h,最后得到f = g + h。如图,节点的右下角为g值,左下角为h值,右上角为f。

选最小值,再次搜索

  1. 选出开启列表中的F值最小的节点,将此节点设为当前节点,移出开启列表,同时加入关闭列表。如图所示。
  2. 取出当前点的相邻点,当相邻点为关闭点或者墙时,不操作。此外,查看相邻点是否在开启列表中,如不在开启列表中将相邻点加入开启列表。如相邻点已经在开启列表中,则需要进行G值判定

G值判定

对于相邻点在开启列表中的,计算相邻点的G值,计算按照当前路径的G值与原开启列表中的G值大小。如果当前路径G值小于原开启列表G值,则相邻点以当前点为父节点,将相邻点与当前点加入追溯表中。同时更新此相邻点的H值。如果当前路径G值大于等于原开启列表G值,则相邻点按照原开启列表中的节点关系,H值不变。因为图示中,当前点G值比原开启列表G值大,因此节点关系按照原父子关系和F值。

计算耗费评分,选最小值

此时计算开启列表中F值最小的点,将此节点设为当前节点,并列最小F值的按添加开启列表顺序,以最新添加为佳。

重复搜索判定工作

直到当goal点B加入开启列表中,则搜索完成。此时事实上生成的路径并一定是最佳路径,而是最快计算出的路径。若判定标准改为当goal点B加入关闭列表中搜索完成,则得出路径是最佳路径,但此时计算量较前者大。

当没有找到goal点,同时开启列表已空,则搜索不到路径。结束搜索。

生产路径

由goal点B向上逐级追溯父节点,追溯至起点A,此时各节点组成的路径即使A*算法生成的最优路径。

官方的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*
Sample code from https://www.redblobgames.com/pathfinding/a-star/
Copyright 2014 Red Blob Games <redblobgames@gmail.com>

Feel free to use this code in your own projects, including commercial projects
License: Apache v2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
*/

#include <iostream>
#include <iomanip>
#include <map>
#include <set>
#include <array>
#include <vector>
#include <utility>
#include <queue>
#include <tuple>
#include <algorithm>
#include <cstdlib>
#include <functional>

struct SimpleGraph {
std::map<char, std::vector<char> > edges;

std::vector<char> neighbors(char id) {
return edges[id];
}
};

SimpleGraph example_graph{ {
{ 'A',{ 'B' } },
{ 'B',{ 'A', 'C', 'D' } },
{ 'C',{ 'A' } },
{ 'D',{ 'E', 'A' } },
{ 'E',{ 'B' } }
} };

struct GridLocation {
int x, y;
};

struct SquareGrid {
static std::array<GridLocation, 4> DIRS;

int width, height;
std::set<GridLocation> walls;

SquareGrid(int width_, int height_)
: width(width_), height(height_) {}

bool in_bounds(GridLocation id) const {
return 0 <= id.x && id.x < width
&& 0 <= id.y && id.y < height;
}

bool passable(GridLocation id) const {
return walls.find(id) == walls.end();
}

std::vector<GridLocation> neighbors(GridLocation id) const {
std::vector<GridLocation> results;

for (GridLocation dir : DIRS) {
GridLocation next{ id.x + dir.x, id.y + dir.y };
if (in_bounds(next) && passable(next)) {
results.push_back(next);
}
}

if ((id.x + id.y) % 2 == 0) {
// aesthetic improvement on square grids
std::reverse(results.begin(), results.end());
}

return results;
}
};

std::array<GridLocation, 4> SquareGrid::DIRS =
{ GridLocation{ 1, 0 }, GridLocation{ 0, -1 }, GridLocation{ -1, 0 }, GridLocation{ 0, 1 } };

// Helpers for GridLocation

bool operator == (GridLocation a, GridLocation b) {
return a.x == b.x && a.y == b.y;
}

bool operator != (GridLocation a, GridLocation b) {
return !(a == b);
}

bool operator < (GridLocation a, GridLocation b) {
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}

std::basic_iostream<char>::basic_ostream& operator<<(std::basic_iostream<char>::basic_ostream& out, const GridLocation& loc) {
out << '(' << loc.x << ',' << loc.y << ')';
return out;
}

// This outputs a grid. Pass in a distances map if you want to print
// the distances, or pass in a point_to map if you want to print
// arrows that point to the parent location, or pass in a path vector
// if you want to draw the path.
template<class Graph>
void draw_grid(const Graph& graph, int field_width,
std::map<GridLocation, double>* distances = nullptr,
std::map<GridLocation, GridLocation>* point_to = nullptr,
std::vector<GridLocation>* path = nullptr) {
for (int y = 0; y != graph.height; ++y) {
for (int x = 0; x != graph.width; ++x) {
GridLocation id{ x, y };
std::cout << std::left << std::setw(field_width);
if (graph.walls.find(id) != graph.walls.end()) {
std::cout << std::string(field_width, '#').c_str();
}
else if (point_to != nullptr && point_to->count(id)) {
GridLocation next = (*point_to)[id];
if (next.x == x + 1) { std::cout << "> "; }
else if (next.x == x - 1) { std::cout << "< "; }
else if (next.y == y + 1) { std::cout << "v "; }
else if (next.y == y - 1) { std::cout << "^ "; }
else { std::cout << "* "; }
}
else if (distances != nullptr && distances->count(id)) {
std::cout << (*distances)[id];
}
else if (path != nullptr && find(path->begin(), path->end(), id) != path->end()) {
std::cout << '@';
}
else {
std::cout << '.';
}
}
std::cout << '\n';
}
}

void add_rect(SquareGrid& grid, int x1, int y1, int x2, int y2) {
for (int x = x1; x < x2; ++x) {
for (int y = y1; y < y2; ++y) {
grid.walls.insert(GridLocation{ x, y });
}
}
}

SquareGrid make_diagram1() {
SquareGrid grid(30, 15);
add_rect(grid, 3, 3, 5, 12);
add_rect(grid, 13, 4, 15, 15);
add_rect(grid, 21, 0, 23, 7);
add_rect(grid, 23, 5, 26, 7);
return grid;
}

struct GridWithWeights : SquareGrid {
std::set<GridLocation> forests;
GridWithWeights(int w, int h) : SquareGrid(w, h) {}
double cost(GridLocation from_node, GridLocation to_node) const {
return forests.find(to_node) != forests.end() ? 5 : 1;
}
};

GridWithWeights make_diagram4() {
GridWithWeights grid(10, 10);
add_rect(grid, 1, 7, 4, 9);
typedef GridLocation L;
grid.forests = std::set<GridLocation>{
L{ 3, 4 }, L{ 3, 5 }, L{ 4, 1 }, L{ 4, 2 },
L{ 4, 3 }, L{ 4, 4 }, L{ 4, 5 }, L{ 4, 6 },
L{ 4, 7 }, L{ 4, 8 }, L{ 5, 1 }, L{ 5, 2 },
L{ 5, 3 }, L{ 5, 4 }, L{ 5, 5 }, L{ 5, 6 },
L{ 5, 7 }, L{ 5, 8 }, L{ 6, 2 }, L{ 6, 3 },
L{ 6, 4 }, L{ 6, 5 }, L{ 6, 6 }, L{ 6, 7 },
L{ 7, 3 }, L{ 7, 4 }, L{ 7, 5 }
};
return grid;
}

template<typename T, typename priority_t>
struct PriorityQueue {
typedef std::pair<priority_t, T> PQElement;
std::priority_queue<PQElement, std::vector<PQElement>,
std::greater<PQElement>> elements;

inline bool empty() const {
return elements.empty();
}

inline void put(T item, priority_t priority) {
elements.emplace(priority, item);
}

T get() {
T best_item = elements.top().second;
elements.pop();
return best_item;
}
};

template<typename Location, typename Graph>
void dijkstra_search
(Graph graph,
Location start,
Location goal,
std::map<Location, Location>& came_from,
std::map<Location, double>& cost_so_far)
{
PriorityQueue<Location, double> frontier;
frontier.put(start, 0);

came_from[start] = start;
cost_so_far[start] = 0;

while (!frontier.empty()) {
Location current = frontier.get();

if (current == goal) {
break;
}

for (Location next : graph.neighbors(current)) {
double new_cost = cost_so_far[current] + graph.cost(current, next);
if (cost_so_far.find(next) == cost_so_far.end()
|| new_cost < cost_so_far[next]) {
cost_so_far[next] = new_cost;
came_from[next] = current;
frontier.put(next, new_cost);
}
}
}
}

template<typename Location>
std::vector<Location> reconstruct_path(
Location start, Location goal,
std::map<Location, Location> came_from
) {
std::vector<Location> path;
Location current = goal;
while (current != start) {
path.push_back(current);
current = came_from[current];
}
path.push_back(start); // optional
std::reverse(path.begin(), path.end());
return path;
}

inline double heuristic(GridLocation a, GridLocation b) {
return std::abs(a.x - b.x) + std::abs(a.y - b.y);
}

template<typename Location, typename Graph>
void a_star_search
(Graph graph,
Location start,
Location goal,
std::map<Location, Location>& came_from,
std::map<Location, double>& cost_so_far)
{
PriorityQueue<Location, double> frontier;
frontier.put(start, 0);

came_from[start] = start;
cost_so_far[start] = 0;

while (!frontier.empty()) {
Location current = frontier.get();

if (current == goal) {
break;
}

for (Location next : graph.neighbors(current)) {
double new_cost = cost_so_far[current] + graph.cost(current, next);
if (cost_so_far.find(next) == cost_so_far.end()
|| new_cost < cost_so_far[next]) {
cost_so_far[next] = new_cost;
double priority = new_cost + heuristic(next, goal);
frontier.put(next, priority);
came_from[next] = current;
}
}
}
}



int main(int argc, const char **argv)
{
GridWithWeights grid = make_diagram4();
GridLocation start{ 1, 4 };
GridLocation goal{ 8, 5 };
std::map<GridLocation, GridLocation> came_from;
std::map<GridLocation, double> cost_so_far;
a_star_search(grid, start, goal, came_from, cost_so_far);
draw_grid(grid, 2, nullptr, &came_from);
std::cout << '\n';
draw_grid(grid, 3, &cost_so_far, nullptr);
std::cout << '\n';
std::vector<GridLocation> path = reconstruct_path(start, goal, came_from);
draw_grid(grid, 3, nullptr, nullptr, &path);
return 0;
}


Reference

http://theory.stanford.edu/~amitp/GameProgramming/

http://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html

https://blog.csdn.net/DinnerHowe/article/details/79380317

https://code.google.com/p/mycodeplayground/

https://www.cnblogs.com/zhoug2020/articles/3468167.html

https://www.cnblogs.com/kanego/archive/2011/08/30/2159070.html
http://qiao.github.io/PathFinding.js/visual/
https://github.com/qiao/PathFinding.js